﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////


#pragma once
#include "GnufBase.h"

class QKeyEvent;

namespace gnuf {

    class HotkeyBlock;
    class HotkeyManager2;
    class HotkeyConfiguration2;
    class ButtonControl;
    class Info;

    struct ModuleHotkeys
    {
        std::string originalVersion;
        std::string originalXmlVersion;
        bool modifiable = false;
        std::wstring originalFilePath;
        std::wstring modifiedFilePath;
    };

    class Hotkey final
    {
        CANNOT_COPY_OR_ASSIGN(Hotkey);

    public:
        static bool isAsSystem(const std::wstring& value);

    public:
        explicit Hotkey(
            HotkeyBlock *,
            const std::wstring & id,
            const std::wstring & value);
        ~Hotkey();

        typedef std::set< std::wstring > SubmitKeys;

        /// \brief Return the submit keys.
        const Hotkey::SubmitKeys & submitKeys() const;

        /// \return Return the Info.
        const Info * info() const;

        /// \brief Query some information. 
        struct QureryResult {
            const Info *    pInfo   = nullptr;
            bool            visible = true;
            bool            enabled = true;
        };
        Hotkey::QureryResult query() const;

        /// \brief Trigger this hotkey to click the button.
        enum class ETriggeredResult : std::uint8_t {
            eSuccessful         = 0,
            eBecauseDonotWant   = 1,
            eBecauseNotFound    = 2,
            eBecauseDisabled    = 3,
        };
        Hotkey::ETriggeredResult trigger() const;

        /// \return Return the pointer to the ModuleHotkeys.
        const ModuleHotkeys * moduleHotkeys() const;
        /// \return Return true if this is from a module.
        bool isFromModule() const;

        /// \return Return the pointer to the HotkeyBlock instance 
        /// that owns really this Hotkey instance.
        HotkeyBlock * block() { return m_pBlock; }
        const HotkeyBlock * block() const { return m_pBlock; }

        /// \return Return true if the hotkey is system, otherwise, return false.
        bool isAsSystem() const;

        /// \return Return the pointer to the HotkeyManager2 instance 
        /// that owns this HotkeyBlock instance.
        HotkeyManager2 * manager();
        const HotkeyManager2 * manager() const;

        /// \return Return the ID through which the command can get found.
        const std::wstring & id() const { return m_id; }

        /// \brief Try to set the value. If failed, the value is not changed, 
        /// the status is cached, and return false.
        /// \param value: The new value.
        /// \param wantToNotify: Want to popup a toast message if the value is
        /// not valid. The default is false.
        bool setValue(const std::wstring & value, bool wantToNotify = false, bool bValidateRepeat = true);

        /// \brief Get the valid value.
        const std::wstring & value() const { return m_value; }

        /// \brief Set display name for a hot key, which is displayed in the configuration pallet
        /// \param name the string to display in the pallet
        void setDisplayName(const std::wstring& name) { m_displayName = name;}

        const std::wstring& displayName() const { return m_displayName; }

        /// \brief Return the valid value as lower case.
        std::wstring lowerCaseValue() const;

        /// \brief Check if cached by the manager.
        bool isCached() const { return m_attributes.test(EAttribute::eCached); }

        struct EAttribute
        {
            static constexpr std::uint8_t eCached               = 0;
            static constexpr std::uint8_t eWantToBeTracked      = 1;
            static constexpr std::uint8_t eWantToBePrompted     = 2;
            static constexpr std::uint8_t eWantToBeTriggered    = 3;
            //-----------------------------------------------------------------
            static constexpr std::uint8_t Number = 4;
        };

        /// \brief Set if the button control wants to get tracked. An untracked
        /// button cannot get triggered through repeating last command.
        void setWantToBeTracked(bool val);
        /// \return Return true if wants to get tracked.
        bool wantToBeTracked() const {
            return m_attributes.test(Hotkey::EAttribute::eWantToBeTracked);
        }

        /// \brief Set if the hotkey wants to get prompted. As present, the user
        /// can get prompted through the message at the bottom toolbar, or the
        /// input box in the canvas.
        void setWantToBePrompted(bool val) {
            m_attributes.set(Hotkey::EAttribute::eWantToBePrompted, val);
        }
        /// \return Return true if wants to get prompted.
        bool wantToBePrompted() const {
            return m_attributes.test(Hotkey::EAttribute::eWantToBePrompted);
        }

        /// \brief Set if the hotkey wants to get triggered.
        /// See also, trigger().
        void setWantToBeTriggered(bool val) {
            m_attributes.set(Hotkey::EAttribute::eWantToBeTriggered, val);
        }
        /// \return Return true if wants to get triggered.
        bool wantToBeTriggered() const {
            return m_attributes.test(Hotkey::EAttribute::eWantToBeTriggered);
        }

        enum class EEventType : std::uint8_t {
            eUnknown = 0,
            eDoubleClickLMButton = 1,   // double click   left mouse button
            eDoubleClickMMButton = 2,   // double click middle mouse button
            eDoubleClickRMButton = 3    // double click  right mouse button
        };
        /// \brief Set if this hotkey wants to handle which type of event.
        void setWantToHandle(Hotkey::EEventType val) { m_wantToHandle = val; }
        /// \return Return the event type that this hotkey wants to handle.
        Hotkey::EEventType wantToHandle() const { return m_wantToHandle; }

    private:
        friend class HotkeyManager2;
        void setCached(bool val) { m_attributes.set(EAttribute::eCached, val); }
        ButtonControl * buttonControl() const;

        HotkeyBlock * m_pBlock;
        const std::wstring m_id;
        std::wstring m_value;
        std::wstring m_displayName;
        std::bitset<EAttribute::Number> m_attributes;
        Hotkey::EEventType m_wantToHandle;
    };

    class HotkeyBlock final
    {
        CANNOT_COPY_OR_ASSIGN(HotkeyBlock);
    public:
        explicit HotkeyBlock(HotkeyManager2 *,
            const std::wstring & moduleGuid);
        ~HotkeyBlock();

        /// \return Return the pointer to the ModuleHotkeys.
        const ModuleHotkeys * moduleHotkeys() const;
        /// \return Return the module Guid.
        const std::wstring & moduleGuid() const { return m_moduleGuid; }
        /// \return Return true if this is from a module.
        bool isFromModule() const { return !m_moduleGuid.empty(); }

        /// \brief Set the caption. Do not allow to set it repeatedly.
        void setCaption(const std::wstring & val);
        /// \return Return the caption.
        const std::wstring & caption() const { return m_caption; }

        /// \brief Set the WhatIsThis. Do not allow to set it repeatedly.
        void setWhatIsThis(const std::wstring & val);
        /// \return Return the whatIsThis.
        const std::wstring & whatIsThis() const { return m_whatIsThis; }

        /// \brief Set the submit key. Do not allow to set it repeatedly.
        void setSubmitKeys(const Hotkey::SubmitKeys &);
        /// \return Return the submit key.
        const Hotkey::SubmitKeys & submitKeys() const { return m_submitKeys; }

        /// \return Return the pointer to the HotkeyManager2 instance 
        /// that owns this HotkeyBlock instance.
        HotkeyManager2 * manager() { return m_pMgr; }
        const HotkeyManager2 * manager() const { return m_pMgr; }

        /// \brief Add a hotkey.
        /// \return Return the valid pointer if successful. Otherwise, return nullptr.
        /// \param id: The ID of this hotkey. This should be equal with a command ID.
        /// \param value: The value of this hotkey, such as Ctrl-Y.
        Hotkey * addHotkey(const std::wstring & id, const std::wstring & value);

        typedef std::list< gcmp::OwnerPtr< Hotkey > > Hotkeys;
        /// \return Return all the blocks.
        const HotkeyBlock::Hotkeys & hotkeys() const { return m_hotkeys; }

    private:
        HotkeyManager2 * m_pMgr;
        const std::wstring m_moduleGuid;
        std::wstring m_caption;
        std::wstring m_whatIsThis;
        Hotkey::SubmitKeys m_submitKeys;
        HotkeyBlock::Hotkeys m_hotkeys;
    };

    class GNUF_BASE_EXPORT HotkeyManager2 final
    {
        CANNOT_COPY_OR_ASSIGN(HotkeyManager2);
    public:
        /// \param isPermanent determine to create a permanent hotkey manager or a temporary one
        /// \note There is only one permanent manager which is constructed in the initialing of workbench,
        ///  and static method 'get' is used to get the very instance.
        /// While a temporary manager is working as a stash for importing hotkeys.
        /// \see WorkBench
        explicit HotkeyManager2(bool isPermanent = false);
        ~HotkeyManager2();

        /// \return Return the global HotkeyManager2 instance which is permanent.
        static HotkeyManager2 * get();

        /// \brief Set maximum char count for hotkey value. Please note that the value smaller than 5 can not be set.
        static void setMaxCharCountForHotkey(std::size_t count) { if(count>5) m_maxCharCountForHotkey = count; };
        static std::size_t maxCharCountForHotkey() { return m_maxCharCountForHotkey; }

        enum class EWorkMode
        {
            eSilence,
            eInputBox
        };
        /// \brief Set the work mode.
        void setPromptMethod(const HotkeyManager2::EWorkMode wm) { m_workMode = wm; }
        /// \return Return the work mode. The default is eInputBox.
        HotkeyManager2::EWorkMode workMode() const { return m_workMode; }

        /// \brief Apply the change on temporary manager to the permanent manager
        void mergeInto(HotkeyManager2* theOther);

        /// \brief Input-Output State.
        enum class EIOState : std::uint8_t
        {
            eIdle,
            // Reading
            eReadingTheOriginal,
            eReadingTheModified,
            // Writing
            eWritingTheModified,
            // SWitching
            eSwitching,
            // Importing
            eImporting,
            // Exporting
            eExporting
        };
        /// \return Return the Input-Output State.
        HotkeyManager2::EIOState ioState() const { return m_ioState; }

        /// \return Return the original version.
        const std::string & originalVersion() const { return m_originalVersion; }

        /// \brief Load the hotkeys.
        void loadHotkeys();

        /// \brief Import hotkeys from given file
        bool importHotkeys(const std::wstring& filePath);

        /// \brief Load the hotkeys from the module.
        /// \param guid: The guid.
        /// \param filePathOfOriginalHotkeys: The absolute xml file path of the 
        /// module for the original hotkeys.
        /// \param filePathOfModifiedHotkeys: The absolute xml file path of the
        /// module for the modified hotkeys.
        /// \return Return true if successful. Otherwise, return false.
        bool loadHotkeysFromModule(
            const std::wstring & guid,
            const std::wstring & filePathOfOriginalHotkeys,
            const std::wstring & filePathOfModifiedHotkeys);

        /// \return Return the ModuleHotkeys by the Guid.
        const ModuleHotkeys * moduleHotkeysBy(const std::wstring & guid) const;

        /// \return Return the Guid of the IO module.
        const std::wstring * ioMoudleGuid() const { return m_pModuleHotkeysIO.guid; }

        /// \brief Save the hotkeys without the ones from the modules.
        /// \param wantSave whether to save the modifications or not 
        void saveHotkeys(bool wantSave = true);

        /// \brief Export the hotkeys to a give path
        void exportHotkeys(const std::wstring& filePath);

        /// \brief Reset the hotkeys.
        void resetHotkeys();

        /// \brief Add a hotkey block.
        /// \return Return the valid pointer if successful. Return nullptr if failed.
        HotkeyBlock * addHotkeyBlock(bool asSystem);

        typedef std::list< gcmp::OwnerPtr< HotkeyBlock > > HotkeyBlocks;
        /// \return Return all hotkey blocks.
        const HotkeyManager2::HotkeyBlocks & hotkeyBlocks() const { return m_blocks; }

        /// \brief Find the hotkey by the value.
        /// \return Return a const pointer to a hotkey. Otherwise, return nullptr.
        const Hotkey * hotkeyByValue(const std::wstring & value) const;
        /// \brief Find the hotkey by the ID.
        /// \return Return a pointer to a hotkey. Otherwise, return nullptr.
        Hotkey * hotkeyByValue(const std::wstring & value);

        /// \brief Find the hotkey by the ID.
        /// \return Return a const pointer to a hotkey. Otherwise, return nullptr.
        const Hotkey * hotkeyById(const std::wstring & id) const;
        /// \brief Find the hotkey by the ID.
        /// \return Return a pointer to a hotkey. Otherwise, return nullptr.
        Hotkey * hotkeyById(const std::wstring & id);

        /// \brief Validate the value. Return true if valid, otherwise return false.
        /// \param value: The value.
        /// \param wantToNotify: If true and not valid, popup a toast message.
        bool validate(const std::wstring & value, bool wantToNotify, bool bValidateRepeat = true) const;

        /// \brief For an internal logic.
        bool onPreRead(const HotkeyConfiguration2 &) const;

        /// \brief Clear the prompt.
        void clearPrompt();

        /// \brief Search hotkeys by the keyword. Every hotkey in the
        /// search result can contain the keyword.
        /// \param keyword: A keyword.
        /// \param onlyWantToBePrompted: Filter out those ones that do not
        /// want to be prompted.
        /// \param[out] result: The search result.
        /// \return Return true if the search result is not empty.
        bool searchHotkeys(
            const std::wstring & keyword,
            const bool onlyWantToBePrompted,
            std::list< std::wstring > & hotkeys) const;

        /// \brief Submit the hotkey.
        void submitHotkey(const std::wstring & hotkey) const;

        /// \brief Check if this Qt key event can submit the hotkey.
        bool canSubmitHotkey(const std::wstring & hotkey, const QKeyEvent *) const;

        /// \brief Handle the Qt key event.
        /// \return Return true if want to stop it being handled further.
        bool onKeyEvent(const QKeyEvent *);

        /// \return Return the hotkey that wants to handle this type of event.
        const Hotkey * hotkeyForHandling(const Hotkey::EEventType) const;

        /// \brief Clear all the owned hotkeys.
        void clear();

        /// \brief Note the state of Hotkey Manager, for permanent or for temporary
        bool isPermanent() const { return m_isPermanent; };

        /// \brief Update the Cache
        void updateHotkeyCache();

    private:
        enum class ESwitchTo
        {
            eUnknown,
            eTheOriginal
        };
        void switchTo(const HotkeyManager2::ESwitchTo);
        bool loadHotkeys(
            const std::wstring & originalFile,
            const std::wstring & modifiedFile,
            std::string & originalVersion,
            std::string & originalXmlVersion,
            bool * pModifiable = nullptr);

        /// \brief accept the modification that is pending in memory, and write to modified xml
        void persistPendingModification();
        /// \brief discard the modification that is pending in memory, reading from modified xml
        void discardPendingModification();

        friend class gnuf::Hotkey;
        void onAdded(Hotkey *);
        void onRemoved(Hotkey *);
        void onChanged(Hotkey *, const std::wstring & oldValue);

        void   cache(Hotkey *);
        void uncache(Hotkey *, const std::wstring & value);

        HotkeyManager2::EWorkMode m_workMode;

        HotkeyManager2::HotkeyBlocks m_blocks;
        const bool m_isPermanent;

        // Cache all the hotkeys as per the empty value or the values and ID(s),
        // to search quickly them.
        typedef std::set< Hotkey * >                EmptyValueHotkeys;
        typedef std::map< std::wstring, Hotkey * >  MapValueToHotkey;
        typedef std::map< std::wstring, Hotkey * >  MapIdToHotkey;
        HotkeyManager2::EmptyValueHotkeys           m_emptyValueHotkeys;
        HotkeyManager2::MapValueToHotkey            m_mapValueToHotkey;
        HotkeyManager2::MapIdToHotkey               m_mapIdToHotkey;

        HotkeyManager2::EIOState m_ioState;

        struct {
            const std::wstring  * guid = nullptr;
            const ModuleHotkeys * data = nullptr;
            ModuleHotkeys * adaptableData = nullptr;
            bool busy() const {
                return (nullptr != this->guid && nullptr != this->data);
            }
            void begin(const std::wstring * _1st, const ModuleHotkeys * _2nd) {
                this->guid = _1st;
                this->data = _2nd;
            }
            void end() {
                this->guid = nullptr;
                this->data = nullptr;
            }
        } m_pModuleHotkeysIO;
        typedef std::map< std::wstring, ModuleHotkeys > MapGuidToModuleHotkeys;
        HotkeyManager2::MapGuidToModuleHotkeys m_mapGuidToModuleHotkeys;

        std::string m_originalVersion;
        std::string m_originalXmlVersion;

        std::wstring m_typing;
        static std::size_t m_maxCharCountForHotkey;
    };

}
